<!doctype html>
<html>
    <head>
        <title>HT 撤销重做手册</title>
        <meta charset="UTF-8">    
        <meta name="viewport" content="user-scalable=yes, width=1024">
        <style type="text/css"> 
            h1, h2, h3, h4, h5, h6, p, blockquote {
                margin: 0;
                padding: 0;
            }
            body {
                font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
                font-size: 13px;
                line-height: 18px;
                color: #737373;
                background-color: white; 
                margin: 10px 13px 10px 13px;
            }
            table {
                margin: 10px 0 15px 0;
                border-collapse: collapse;
            }
            td,th {	
                border: 1px solid #ddd;
                padding: 3px 10px;
            }
            th {
                padding: 5px 10px;	
            }
            a {
                color: #0069d6;
            }
            a:hover {
                color: #0050a3;
                text-decoration: none;
            }
            a img {
                border: none;
            }
            p {
                margin-bottom: 9px;
            }
            h1, h2, h3, h4, h5, h6 {
                color: #404040;
                line-height: 36px;
            }
            h1 {
                margin-bottom: 18px;
                font-size: 30px;
            }
            h2 {
                font-size: 24px;
            }
            h3 {
                font-size: 18px;
            }
            h4 {
                font-size: 16px;
            }
            h5 {
                font-size: 14px;
            }
            h6 {
                font-size: 13px;
            }
            hr {
                margin: 0 0 19px;
                border: 0;
                border-bottom: 1px solid #ccc;
            }
            blockquote {
                padding: 13px 13px 21px 15px;
                margin-bottom: 18px;
                font-family:georgia,serif;
                font-style: italic;
            }
            blockquote:before {
                content:"\201C";
                font-size:40px;
                margin-left:-10px;
                font-family:georgia,serif;
                color:#eee;
            }
            blockquote p {
                font-size: 14px;
                font-weight: 300;
                line-height: 18px;
                margin-bottom: 0;
                font-style: italic;
            }
            code, pre {
                font-family: Monaco, Andale Mono, Courier New, monospace;
            }
            code {
                background-color: #fee9cc;
                color: rgba(0, 0, 0, 0.75);
                padding: 1px 3px;
                font-size: 12px;
                border-radius: 3px;
            }
            pre {
                display: block;
                padding: 14px;
                margin: 0 0 18px;
                line-height: 16px;
                font-size: 11px;
                border: 1px solid #d9d9d9;
                white-space: pre-wrap;
                word-wrap: break-word;
            }
            pre code {
                background-color: #fff;
                color:#737373;
                font-size: 11px;
                padding: 0;
            }
            sup {
                font-size: 0.83em;
                vertical-align: super;
                line-height: 0;
            }
            * {
                -webkit-print-color-adjust: exact;
            }
            @media screen and (min-width: 914px) {
                body {
                    width: 854px;
                    margin:10px auto;
                }
            }
            @media print {
                body,code,pre code,h1,h2,h3,h4,h5,h6 {
                    color: black;
                }
                table, pre {
                    page-break-inside: avoid;
                }
            }                        
            iframe{
                width: 100%;
                border: 1px solid #34495E;
                margin: 0;                
            }            
            .logo{
                vertical-align: middle;
            }

        </style>        
        <script>
            function init() {
                var logoSrc = '',
                    logos = document.querySelectorAll('.logo'),
                    i = 0;
                for (; i < logos.length; i++) {
                    logos[i].src = logoSrc;
                }
                
                var iframes = document.querySelectorAll('iframe'),
                    func = function(){};
                for (i=0; i < iframes.length; i++) {
                    var iframe = iframes[i];
                    
                    // a small hack to make it work on android
                    iframe.ontouchstart = func;
                    
                    var div = document.createElement('div');
                    div.innerHTML = '<a href="' + iframe.src + '" target="_blank">独立运行→</a>';
                    iframe.parentNode.insertBefore(div, iframe);
                }   
            }
        </script>    
    </head>
    <body onload="init();">
        <a href="http://www.hightopo.com"><img class="logo"></a>HT 撤销重做手册
        <hr style="margin: 1px 0 20px 0">
<p>索引</p>

<ul><li><a href="#ref_description">概述</a></li><li><a href="#ref_sample">简单示例</a></li></ul>

<hr/>

<div id="ref_description"></div>

<h3>概述</h3>

<p>做一个全功能的拓扑编辑器或<code>3D</code>场景编辑器，<code>撤销和重做</code>功能是不可或缺的，想象一下；用户费劲心思设计了一个图元，然后一不小心按到了<code>delete</code>键，如果不能撤销，用户只能欲哭无泪了。</p>

<p>此插件提供了<code>ht.HistoryManager</code>类，监听记录<code>DataModel</code>中图元的属性变化并提供<code>API</code>接口撤销或重做历史记录。在正式使用<code>API</code>之前，您的页面里需要引入相关的<code>js</code>文件：</p>

<pre><code>&lt;script src=&quot;ht.js&quot;&gt;&lt;/script&gt; &lt;!--先引入ht.js--&gt;
&lt;script src=&quot;ht-historymanager.js&quot;&gt;&lt;/script&gt;</code></pre>

<p>使用方式非常简单，创建一个<code>HistoryManager</code>并绑定<code>DataModel</code>即可：</p>

<pre><code>var historyManager = new ht.HistoryManager(dataModel);</code></pre>

<p><code>dataModel</code>中所有图元的属性变化都会被<code>historyManager</code>对象记录，当要撤销操作时，调用:</p>

<pre><code>historyManager.undo();</code></pre>

<p>要重做时，调用</p>

<pre><code>historyManager.redo();</code></pre>

<p>用户无法直接操作<code>API</code>，所以通常把上面<code>API</code>绑定到页面的快捷键上：</p>

<pre><code>window.addEventListener(&#39;keydown&#39;, function(e) {
    if (e.ctrlKey) {
        if (e.keyCode == 90) {//ctrl + z
            historyManager.undo();
        } else if (e.keyCode == 89) {//ctrl + y
            historyManager.redo();
        }
    }
})</code></pre>

<p><code>ht.HistoryManager</code>提供的<code>API</code>如下：</p>

<ul><li><code>dataModel</code> 通过<code>setDataModel(dataModel)</code>和<code>getDataModel()</code>操作，监听并记录此<code>dataModel</code>中所有图元的属性变化</li><li><code>getHistories()</code> 获得历史记录数组</li><li><code>historyIndex</code> 表示当前所处的记录下标，比如用户做了<code>10</code>次操作，历史记录数组中有<code>10</code>条记录，<code>historyIndex</code>指向最后一条记录，值为<code>9</code>(数组下标从0开始)，<code>undo</code>一次，<code>historyIndex</code>往前移动，变成<code>8</code>；<code>redo</code>一次，<code>historyIndex</code>往后移动，再次变成<code>9</code>，也可以直接调用<code>setHistoryIndex(newIndex)</code>跳到指定的历史记录。</li><li><code>undo()</code> 恢复操作，<code>historyIndex</code>往前移动一次</li><li><code>redo()</code>重做操作，<code>historyIndex</code>往后移动一次</li><li><code>beginInteraction()</code> 类似数据库里开启事务，从<code>beginInteraction()</code>到<code>endInteraction()</code>之间所有的修改可被一次性撤销或重做</li><li><code>endInteraction()</code> 类似数据库里结束事务，从<code>beginInteraction()</code>到<code>endInteraction()</code>之间所有的修改可被一次性撤销或重做</li><li><code>beginTransient()</code> 此方法调用之后，<code>endTransient()</code>被调用之前，<code>dataModel</code>中图元的属性变化不会被记录</li><li><code>endTransient()</code> <code>beginTransient()</code>调用之后，此方法被调用之前，<code>dataModel</code>中图元的属性变化不会被记录</li><li><code>maxHistoryCount</code> 通过<code>setMaxHistoryCount(newCount)</code>和<code>getMaxHistoryCount()</code>操作，表示最大的历史记录条数，默认为<code>10</code>，只记录用户<code>10</code>次最近的操作(旧记录被替换)</li><li><code>clear()</code> 清除掉所有的历史记录</li></ul>

<div id="ref_sample"></div>

<h3>简单示例</h3>

<p>接下来看一个例子：</p>

<p><iframe src="examples/example_demo.html" style="height:600px"></iframe></p>

<p>这个例子里，左上角是一个<code>Palette</code>组件，可以拖动其中的<code>Node</code>到中间的拓扑来创建<code>Node</code>；左下角是一个<code>TreeView</code>组件，可以呈现父子关系；
中间是拓扑组件，可以通过拖拽等方式操作改变图元属性；右上角是一个属性页组件，可以编辑<code>label</code>和<code>label</code>背景色；右下角是一个<code>ListView</code>，展示<code>HistoryManager</code>中的历史记录，选中不同的记录还可以跳转历史记录。</p>

<pre><code>historyManager.mp(function(e) {//监听historyManager属性变化，把历史记录同步到右下角的ListView中展示
    var property = e.property;
    if (property === &#39;historyIndex&#39; || property === &#39;histories&#39;) {
        var histories = historyManager.getHistories(),
            historyIndex = historyManager.getHistoryIndex(),
            dataModel = list.dm();
        list._betweenUpdate = 1;
        dataModel.clear();
        var initData = new ht.Data();
        initData.setName(&#39;init status&#39;);
        dataModel.add(initData);
        histories.forEach(function(history) {
            var actions = [];
            history.forEach(function(action) {
                actions.push(action.type);
            });
            var actionsStr = actions.join(&#39;, &#39;);
            var data = new ht.Data();
            data.setName(actionsStr);
            dataModel.add(data);
        });
        list.sm().ss(dataModel.getDatas().get(historyIndex + 1));
        list._betweenUpdate = 0;
    }
});

list.sm().ms(function(e) {//监听右下角ListView选中状态变化，跳转到相应的历史记录
    if (!list._betweenUpdate) {
        var data = list.sm().ld(),
            index = list.dm().getDatas().indexOf(data);
        historyManager.setHistoryIndex(index - 1);
    }
});

window.addEventListener(&#39;keydown&#39;, function(e) {//增加快捷键支持
    if (e.ctrlKey) {
        if (e.keyCode == 90) {//ctrl+z 撤销
            historyManager.undo();
        } else if (e.keyCode == 89) {//ctrl+y 重做
            historyManager.redo();
        }
    }
})</code></pre>    <hr id="contact" style="margin: 20px 0 1px 0">
    <a href="http://www.hightopo.com"><img class="logo"></a>欢迎交流 <a href="mailto:service@hightopo.com">service@hightopo.com</a>
</body>
</html>
